using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;

using zijian666.AnyExtensions.Privates;

namespace zijian666.AnyExtensions;

/// <summary>
/// 集合相关的扩展方法
/// </summary>
public static class CollectionExtensions
{
    /// <summary>
    /// 返回排除空对象（null）后的去重集合
    /// </summary>
    internal static ICollection DistinctWithoutNull(this IEnumerable? source)
    {
        if (source is null)
        {
            return GenericHelper<object>.EmptyArray;
        }
        var collection = source as ICollection;
        if (collection?.Count == 0)
        {
            return collection;
        }
        if (collection?.Count == 1)
        {
            if (collection is IList list)
            {
                return list[0] is null ? GenericHelper<object>.EmptyArray : collection;
            }
            foreach (var item in collection)
            {
                return item is null ? GenericHelper<object>.EmptyArray : collection;
            }
        }
        return source.OfType<object>().Distinct().Where(x => x is not null).ToList();
    }

    /// <summary>
    /// 返回排除空对象（null）后的去重集合
    /// </summary>
    internal static ICollection<T> DistinctWithoutNull<T>(this IEnumerable<T>? source)
    {
        if (source is null)
        {
            return GenericHelper<T>.EmptyArray;
        }
        var collection = source as ICollection<T>;
        if (collection?.Count == 0)
        {
            return GenericHelper<T>.EmptyArray;
        }
        if (collection?.Count == 1)
        {
            return collection.FirstOrDefault() is null ? GenericHelper<T>.EmptyArray : collection;
        }
#pragma warning disable CS8604 // 引用类型参数可能为 null。
        if (GenericHelper<T>.CanNull && collection is ISet<T> set && !collection.Contains(default))
#pragma warning restore CS8604 // 引用类型参数可能为 null。
        {
            return collection;
        }
        return source.Distinct().Where(x => x is not null).ToList();
    }

    /// <summary>
    /// 从字典中批量获取对象
    /// </summary>
    public static IEnumerable<TValue> GetValues<TKey, TValue>(this IDictionary<TKey, TValue> dict, params TKey[] keys)
        => GetValues(dict, (IEnumerable<TKey>)keys);

    /// <summary>
    /// 从字典中批量获取对象
    /// </summary>
    public static IEnumerable<TValue> GetValues<TKey, TValue>(this IDictionary<TKey, TValue> dict, IEnumerable<TKey> keys)
    {
        if (keys is not null)
        {
            foreach (var key in keys.Distinct())
            {
                if (key is not null && dict.TryGetValue(key, out var value))
                {
                    yield return value;
                }
            }
        }
    }

    /// <summary>
    /// 从字典中批量获取对象
    /// </summary>
    public static IEnumerable<TValue> GetValues<TKey, TValue>(this IDictionary<TKey, TValue> dict, params TKey?[] keys)
        where TKey : struct => GetValues(dict, (IEnumerable<TKey?>)keys);

    /// <summary>
    /// 从字典中批量获取对象
    /// </summary>
    public static IEnumerable<TValue> GetValues<TKey, TValue>(this IDictionary<TKey, TValue> dict, IEnumerable<TKey?> keys)
        where TKey : struct
    {
        if (keys is not null)
        {
            foreach (var key in keys.Distinct())
            {
                if (key.HasValue && dict.TryGetValue(key.Value, out var value))
                {
                    yield return value;
                }
            }
        }
    }

    /// <summary>
    /// 比较2个数组是否一致
    /// </summary>
    /// <param name="array1"></param>
    /// <param name="array2"></param>
    /// <returns></returns>
    public static bool IsSame(this IEnumerable array1, IEnumerable array2, bool inOrder = true)
    {
        if (array1 == array2)
        {
            return true;
        }
        if (array1 is null || array2 is null)
        {
            return false;
        }
        if (inOrder == false)
        {
            array1 = array1.Cast<object>().OrderBy(x => x.GetHashCode());
            array2 = array2.Cast<object>().OrderBy(x => x.GetHashCode());
        }
        var e1 = array1.GetEnumerator();
        var e2 = array2.GetEnumerator();
        while (true)
        {
            var a = e1.MoveNext();
            var b = e2.MoveNext();
            if (a != b)
            {
                return false;
            }
            if (!a)
            {
                return true;
            }
            if (!Equals(e1.Current, e2.Current))
            {
                return false;
            }
        }
    }

    /// <summary>
    /// 判断一个集合是否为空
    /// </summary>
    public static bool IsNullOrEmpty([NotNullWhen(false)] this IEnumerable? value)
    {
        if (value is null)
        {
            return true;
        }
        if (value is ICollection collection)
        {
            return collection.Count == 0;
        }
        foreach (var _ in value)
        {
            return false;
        }
        return true;
    }

    /// <summary>
    /// 判断一个集合是否为空
    /// </summary>
    public static bool IsNullOrEmpty<T>([NotNullWhen(false)] this IEnumerable<T>? value)
    {
        if (value is null)
        {
            return true;
        }
        if (value is ICollection<T> collection)
        {
            return collection.Count == 0;
        }
        if (value is ICollection collection2)
        {
            return collection2.Count == 0;
        }
        foreach (var _ in value)
        {
            return false;
        }
        return true;
    }

    /// <summary>
    /// 调用 <seealso cref="string.Join(string, IEnumerable{string})" 生成新的字符串/>,
    /// 如果 <paramref name="source"/> 是null则返回空字符串 <seealso cref="string.Empty"/>
    /// </summary>
    public static string Join(this IEnumerable? source, string separator = ",")
        => source is null ? string.Empty : string.Join(separator ?? ",", source.Cast<object>());

    /// <summary>
    /// 调用 <seealso cref="string.Join(string, IEnumerable{string})" 生成新的字符串/>,
    /// 如果 <paramref name="source"/> 是null则返回空字符串 <seealso cref="string.Empty"/>
    /// </summary>
    public static string Join<T>(this IEnumerable<T>? source, string separator = ",")
        => source is null ? string.Empty : string.Join(separator ?? ",", source);

    /// <summary>
    /// 返回 <seealso cref="HashSet{TSource}"/>
    /// </summary>
    /// <exception cref="ArgumentNullException"> <paramref name="valueSelector"/> is null</exception>
    public static HashSet<TValue> ToHashSet<TSource, TValue>(this IEnumerable<TSource> source, Func<TSource, TValue> valueSelector, IEqualityComparer<TValue>? comparer = null)
    {
        if (valueSelector is null)
        {
            throw new ArgumentNullException(nameof(valueSelector));
        }

        return source.Select(valueSelector).ToHashSet();
    }
#if !NET21_OR_GREATER && !NETCOREAPP2_0_OR_GREATER && !NET472_OR_GREATER && !NET5_0_OR_GREATER
    /// <summary>
    /// 返回 <seealso cref="HashSet{TSource}"/>
    /// </summary>
    public static HashSet<T> ToHashSet<T>(this IEnumerable<T>? source, IEqualityComparer<T>? comparer = null)
    {
        if (source is null)
        {
            // 为了保持与 系统的行为一致，这里抛出异常
            throw new ArgumentNullException(nameof(source));
        }
        return new HashSet<T>(source, comparer);
    }

    /// <summary>
    /// 根据 <paramref name="key"/> 获取 <seealso cref="IDictionary{TKey, TValue}" 的值, 如果不存在则返回 <paramref name="defaultValue"/> />
    /// </summary>
    /// <returns></returns>
    public static TValue? GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue? defaultValue = default)
        where TKey : class
    {
        try
        {
            return dict.TryGetValue(key, out var value) ? value : defaultValue;
        }
        catch
        {
            return defaultValue;
        }
    }

#endif

    /// <summary>
    /// 根据 <paramref name="key"/> 获取 <seealso cref="IDictionary{TKey, TValue}" 的值, 如果不存在则返回 <paramref name="defaultValue"/> />
    /// </summary>
    /// <returns></returns>
    public static TValue? GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey? key, TValue? defaultValue = default)
        where TKey : struct
    {
        try
        {
            return key is not null && dict.TryGetValue(key.Value, out var value) ? value : defaultValue;
        }
        catch
        {
            return defaultValue;
        }
    }

    /// <summary>
    /// 返回 <seealso cref="HashSet{string}"/>
    /// </summary>
    /// <exception cref="ArgumentNullException"> <paramref name="valueSelector"/> is null</exception>
    public static HashSet<string> ToHashSet<TSource>(this IEnumerable<TSource> source, Func<TSource, string> valueSelector, CompareOptions options, CultureInfo? culture = null)
    {
        if (valueSelector is null)
        {
            throw new ArgumentNullException(nameof(valueSelector));
        }
        var comparer = culture.GetStringComparer(options);
        return ToHashSet(source, valueSelector, comparer);
    }

    /// <summary>
    /// 返回 <seealso cref="HashSet{string}"/>
    /// </summary>
    public static HashSet<string> ToHashSet(this IEnumerable<string> source, CompareOptions options, CultureInfo? culture = null)
    {
        var comparer = culture.GetStringComparer(options);
        return source.ToHashSet(comparer);
    }


    /// <summary>
    /// 返回 <seealso cref="HashSet{TSource}"/>
    /// </summary>
    /// <exception cref="ArgumentNullException"> <paramref name="valueSelector"/> is null</exception>
    public static HashSet<TValue> AsHashSet<TSource, TValue>(this IEnumerable<TSource>? source, Func<TSource, TValue> valueSelector, IEqualityComparer<TValue>? comparer = null)
    {
        if (valueSelector is null)
        {
            throw new ArgumentNullException(nameof(valueSelector));
        }

        return source?.Select(valueSelector).AsHashSet() ?? new HashSet<TValue>(comparer);
    }

    /// <summary>
    /// 返回 <seealso cref="HashSet{TSource}"/>
    /// </summary>
    public static HashSet<T> AsHashSet<T>(this IEnumerable<T>? source, IEqualityComparer<T>? comparer = null)
    {
        if (source is null)
        {
            return [];
        }
        if (source is HashSet<T> hashset && (hashset.Comparer == comparer || comparer is null))
        {
            return hashset;
        }
        return new HashSet<T>(source, comparer);
    }

    /// <summary>
    /// 返回 <seealso cref="HashSet{string}"/>
    /// </summary>
    /// <exception cref="ArgumentNullException"> <paramref name="valueSelector"/> is null</exception>
    public static HashSet<string> AsHashSet<TSource>(this IEnumerable<TSource>? source, Func<TSource, string> valueSelector, CompareOptions options, CultureInfo? culture = null)
    {
        if (valueSelector is null)
        {
            throw new ArgumentNullException(nameof(valueSelector));
        }
        var comparer = culture.GetStringComparer(options);
        return AsHashSet(source, valueSelector, comparer);
    }

    /// <summary>
    /// 返回 <seealso cref="HashSet{string}"/>
    /// </summary>
    public static HashSet<string> AsHashSet(this IEnumerable<string>? source, CompareOptions options, CultureInfo? culture = null)
    {
        var comparer = culture.GetStringComparer(options);
        return source.AsHashSet(comparer);
    }

    /// <summary>
    /// 排除集合中的空对象（null）
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"></param>
    /// <returns></returns>
    public static IEnumerable<T> WithoutNull<T>(this IEnumerable<T?>? source)
    => source?.Where(x => x is not null) ?? GenericHelper<T>.EmptyArray;

    /// <summary>
    /// 排除集合中的空对象（null）
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"></param>
    /// <returns></returns>
    public static IEnumerable<T> WithoutNull<T>(this IEnumerable<T?>? source)
        where T : struct
        => source?.Where(x => x is not null).Select(x => x!.Value) ?? GenericHelper<T>.EmptyArray;

    /// <summary>
    /// 排除集合中的空字符串
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static IEnumerable<string> WithoutEmpty(this IEnumerable<string?>? source)
        => source?.Where(x => !string.IsNullOrEmpty(x)) ?? GenericHelper<string>.EmptyArray;

    /// <summary>
    /// 排除集合中的空白字符串
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static IEnumerable<string> WithoutWhiteSpace(this IEnumerable<string?>? source)
        => source?.Where(x => !string.IsNullOrWhiteSpace(x)) ?? GenericHelper<string>.EmptyArray;

    /// <summary>
    /// 排除集合中的默认值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"></param>
    /// <returns></returns>
    public static IEnumerable<T> WithoutDefaultValue<T>(this IEnumerable<T>? source)
        where T : struct
        => source?.Where(x => !default(T).Equals(x)) ?? GenericHelper<T>.EmptyArray;

    /// <summary>
    /// 排除集合中的默认值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"></param>
    /// <returns></returns>
    public static IEnumerable<T> WithoutDefaultValue<T>(this IEnumerable<T?> source)
        where T : struct
        => source?.Select(x => x ?? default).WithoutDefaultValue() ?? GenericHelper<T>.EmptyArray;

    /// <summary>
    /// 转为只读集合
    /// </summary>
    public static IReadOnlyCollection<T> AsReadOnly<T>(this IEnumerable<T>? source)
    {
        if (source is null)
        {
            return GenericHelper<T>.ReadOnlyCollection;
        }
        return source as IReadOnlyCollection<T> ?? new ReadOnlyCollection<T>(source as IList<T> ?? source.ToList());
    }

    /// <summary>
    /// 转为只读集合
    /// </summary>
    public static IReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source)
        where TKey : notnull
    {
        if (source is null)
        {
            return new ReadOnlyDictionary<TKey, TValue>(new Dictionary<TKey, TValue>());
        }
        return source as IReadOnlyDictionary<TKey, TValue> ?? new ReadOnlyDictionary<TKey, TValue>(source as IDictionary<TKey, TValue> ?? source.ToDictionary(x => x.Key, x => x.Value));
    }

    /// <summary>
    /// 循环集合
    /// </summary>
    public static IEnumerable<T>? Each<T>(this IEnumerable<T>? source, Action<T> action)
    {
        if (source is null || action is null)
        {
            return source;
        }
        foreach (var item in source)
        {
            action(item);
        }
        return source;
    }

    /// <summary>
    /// 循环集合
    /// </summary>
    public static IEnumerable<T>? Each<T>(this IEnumerable<T>? source, Action<int, T> action)
    {
        if (source is null || action is null)
        {
            return source;
        }
        var i = 0;
        foreach (var item in source)
        {
            action(i, item);
            i++;
        }
        return source;
    }

    /// <summary>
    /// 筛选集合中某个属性值包含在另一集合中的数据
    /// </summary>
    public static IEnumerable<TSource> FindByIn<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IEnumerable<TKey> values)
    {
        if (source is null || values is null)
        {
            return GenericHelper<TSource>.EmptyArray;
        }

        if (selector is null)
        {
            throw new ArgumentNullException(nameof(selector));
        }

        var hashset = values.AsHashSet();
        if (hashset.Count == 0)
        {
            return GenericHelper<TSource>.EmptyArray;
        }

        if (hashset.Count > 1)
        {
            return source.Where(x => values.Contains(selector(x)));
        }

        var value = hashset.First();

        if (value is null)
        {
            return source.Where(x => selector(x) is null);
        }
        else
        {
            return source.Where(x => Equals(value, selector(x)));
        }
    }

    /// <summary>
    /// 筛选集合中某个属性值不包含在另一集合中的数据
    /// </summary>
    public static IEnumerable<TSource> FindByNotIn<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IEnumerable<TKey> values)
    {
        if (source is null || values is null)
        {
            return source ?? GenericHelper<TSource>.EmptyArray;
        }

        if (selector is null)
        {
            throw new ArgumentNullException(nameof(selector));
        }

        var hashset = values.AsHashSet();
        if (hashset.Count == 0)
        {
            return source;
        }

        if (hashset.Count > 1)
        {
            return source.Where(x => !values.Contains(selector(x)));
        }

        var value = hashset.First();

        if (value is null)
        {
            return source.Where(x => selector(x) is not null);
        }
        else
        {
            return source.Where(x => !Equals(value, selector(x)));
        }
    }

    /// <summary>
    /// 判断条件后确定是否执行查询表达式
    /// </summary>
    public static IEnumerable<TSource> WhereIf<TSource>(this IEnumerable<TSource> source, bool condition, Func<TSource, bool>? predicate)
        => WhereIf(source, condition, predicate, null);

    /// <summary>
    /// 判断条件后确定是否执行查询表达式
    /// </summary>
    public static IEnumerable<TSource> WhereIf<TSource>(this IEnumerable<TSource> source, bool condition, Func<TSource, bool>? truePredicate, Func<TSource, bool>? falsePredicate)
    {
        if (source is null)
        {
            return GenericHelper<TSource>.EmptyQueryable;
        }
        var predicate = condition ? truePredicate : falsePredicate;

        if (predicate is null)
        {
            return source;
        }
        return source.Where(predicate);
    }

    /// <summary>
    /// 判断条件后确定是否执行查询操作
    /// </summary>
    public static IEnumerable<TSource> If<TSource>(this IEnumerable<TSource> source, bool condition, Func<IEnumerable<TSource>, IEnumerable<TSource>>? truthy, Func<IEnumerable<TSource>, IEnumerable<TSource>>? falsy = null)
    {
        if (source is null)
        {
            return GenericHelper<TSource>.EmptyQueryable;
        }
        return ObjectExtensions.If(source, condition, truthy, falsy) ?? GenericHelper<TSource>.EmptyQueryable;
    }
}